;   This program is free software: you can redistribute it and/or modify
;   it under the terms of the GNU General Public License as published by
;   the Free Software Foundation, either version 3 of the License, or
;   (at your option) any later version.
;
;   This program is distributed in the hope that it will be useful,
;   but WITHOUT ANY WARRANTY; without even the implied warranty of
;   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;   GNU General Public License for more details.
;
;   You should have received a copy of the GNU General Public License
;   along with this program.  If not, see <http://www.gnu.org/licenses/>.
;
;程序名称:clock.asm
;功能:常驻计时器/闹钟
;环境:16BIT DOS实模式
;编译器:MASM 5.1-6X
;用法:看说明
;返回值:没有
;破坏寄存器:不适用
;
;
;这是吧上某人的功课吧
;这题目有普遍性，涉及的问题较多，所以独立发一帖，希望多些人看到．
;
;参照 “定时响铃”的例子及其它MASM程序的例子,实现 INT 1C
;MYINT1C 实现的功能在屏幕的右上角显示秒表或时钟，按ESC，
;退出程序。
;说明：
;1.实现基本的秒表功能，即参照8.5范例给出的响铃程序改写，
;实现简单的计时和暂停、中止等功能；
;2.时钟复杂一些，按照书后BIOS/DOS中断附表实现从系
;统取时间，并能够对其进行设置和相应的计时功能。
;
;解题
;DOS的常驻，时钟比较简单，基本上拦截1CH，抓取系统时间，
;找个地方显示一下就完事了，至于计时，方法也有许多，1CH
;本身就是每1/18.2秒运行一次，所以同时也有计时功能．
;常驻的方法用DOS的21H,AH=31H或者INT27也可以．
;这个小小CLOCK程式的计时和读时间不用任何中断int21h,ah=2ch
;或int 1Ah,而是直接读取40:6C - 40:6D的系统累加时间，这相容度好像
;还大，测试在windows/dos 和dosbox都能正常运行
;编译：因为程式是com，masm 5.x 须要exe2bin  转成com>　exe2bin clock.exe clock.com
;masm 6.x的话加　ml /AT　的设定可直接转成com
;
;用法：
;clock   ;在右上角显示时间
;clock u  ; 移除常驻
;clock s mm:ss
;mm是分钟，ss是秒
;键入clock s 3:10  　
;右上角显示倒数计时3:10 ..3:09...3:08 直到00:00时显示3秒红色 -alarm-文字
;然后回到正常系统时间
;若键入　clock s 10，程式会视为倒数10分钟，若只要倒数秒
;可键入　clock s 0:10 ;则数10秒
;
;若clock已经常驻，也可以键入clock s mm:ss 去设定新的倒数
;
;
;這個和之前的little_clock是一樣的，只是加了中文解釋，為免影響編譯，所以另開一個
;若有編譯錯誤，請下載little_clock.
;
'
.286
REMOVE_AX       EQU     0AA01H ;移除标志
ASK_AX          EQU     0AA02H ;询问是否已驻留
Alarm_set_ask   equ     0AA03H ;设定闹钟标志
EXIST_FLAG      EQU     0AAFFH ;已驻留标志
time_att        EQU     70H ;时钟颜色
alarm_att       EQU     0cfH ;闹钟颜色
alarm_time      equ     18*3    ; ?  alarm seconds 闹钟时间 3秒

                cseg    segment
                assume cs:cseg
                org     100h
begin:          jmp     INIT

OLD_OFF         DW      0 ;旧1c中断offset
OLD_SEG         DW      0 ;旧1c中断segment
delay_count     dw      0 ;延迟计数
work_flag       db      0 ;工作中标志
alarm_flag    db	0		;闹钟标志
alarm_count     dw      alarm_time ;闹钟计数

;------------- TSR start ------------------

INT_START:      STI
                cmp     cs:work_flag,0 ;是否工作中
                jz      j10	;不是
                iret ;工作中,返回

j10:            mov     cs:work_flag,1 ;设定工作中flag

		PUSH	ES
                PUSH    SI
                PUSH    DI
                PUSH    BP
                PUSHA
                CMP     AX,REMOVE_AX ; Remove ? ;是否移除
                JNZ     J20 ;不是
                POPA  
                MOV     AX,CS
                MOV     BX,EXIST_FLAG ;回复已驻留标志
                MOV     CX,CS:OLD_OFF ;回传旧1ch中断offset
                MOV     DX,CS:OLD_SEG ;回传旧1ch中断segment
                JMP     J110
J20:
                CMP     AX,ASK_AX ;询问驻留
                JNZ     J50 ;不是
                POPA
                MOV     BX,EXIST_FLAG ;回复已驻留标志
                JMP     J110
                ; DO SHOW TIME

J50:            cmp     ax,Alarm_set_ask ;是否设定闹钟
                jnz     j52 ;不是
                mov     delay_count,cx ;存入闹钟计时
                dec     alarm_count ;启动3秒闹钟alarm
                ;
j52:
                cmp     delay_count,0 ;闹钟计数是否到0
                jnz     j53 ;不

                cmp     alarm_count,alarm_time;3秒闹钟是否已启动
                jz      j60 ;仍未启动
                jmp     short j55

.386

j53:            xor     eax,eax
                dec     delay_count ;倒数闹钟计数
                mov     ax,delay_count ;存入闹钟计数,准备印出
                jnz     j70 ;未到0

                ;       time_up
j55:
                dec     alarm_count  ;倒数闹钟计数0,开始3秒alarm倒数,alarm完了没
                jz      j60             ;normal,完了,回复正常时钟显示

j59:		mov	bp,offset alarm_str ;alarm文字地址
		mov	bl,alarm_att ;闹钟颜色
                mov     ax,cs
                mov     es,ax  ;对齐cs,es,准备印出es:bp字串
                jmp     short j90


j60:            mov     ax,40h ;系统段
		mov	es,ax ;存段
		mov	eax,dword ptr es:[006ch] ;6c-6d,dword,取系统计时,由凌辰开始每秒18.2次累加1.这个数即总秒数
		
j70:		mov	ebx,10  ;eax * 10 / 182 增加准确
		mul	ebx
		mov	ebx,182
		xor	edx,edx
		div	ebx   ;total seconds  ;得总秒数
		mov	ebx,60  
		xor	edx,edx
		div	ebx   ;ax = min / edx = seconds  ;总秒数/60 ;eax商是总分钟,edx是剩余秒数
		mov	cl,dl ;second ;保存
		xor	edx,edx
		div	ebx   ;ax = hour / edx = min ;总分钟 / 60, eax商是小时,edx是剩余分钟 

.286
		mov	di,cs
		mov	es,di
		mov	di,offset time_str ;时钟字串地址
		mov	bp,di
		add	al,0   ;加法,准备做BCD 调整
                aam  ;bcd加法调整,将al的小时数,转为BCD格式
		or	ax,3030h ;转为ASCII格式
		xchg	ah,al ;交换
                stosw ;存在小时地址
		inc	di  ;跳一位
		mov	al,dl  ;取分钟
		add	al,0   ;加法,准备做BCD 调整
                aam ;bcd加法调整,将al的小时数,转为BCD格式
		or	ax,3030h  ;转为ASCII格式
		xchg	ah,al  ;交换
                stosw ;存分钟地址
		inc	di 
		mov	al,cl ;取秒
		add	al,0  ;和上面一样
                aam
		or	ax,3030h
		xchg	ah,al
                stosw
                mov     alarm_count,alarm_time  ;reset 重置闹钟3秒
                mov     bl,time_att ;颜色

j90:            mov     dh,0 ;行
		mov	dl,80-8 ;列位置
		mov	cx,8 ;字数
		mov	bh,0  ;显示页
		mov	ax,1300h ;印出 es:bp字串
		int	10h

J100:
                POPA

J110:		POP	 BP
		POP	 DI
		POP	 SI
		POP	 ES
                mov      cs:work_flag,0 ;重置不在工作中的标志
                IRET ;返回 

;------------------------------------------------

time_str	db	'00:00:00',0   ;时钟字串
alarm_str	db	' -Alarm-',0  ;闹钟字串

tsr_end 	equ	$

NOT_EXIST       DB      'Clock not install!','$'
RELEASE_STR     DB      'Clock Removed!','$'
EXIST_STR       DB      'Clock already installed!','$'
install_str     DB      'Clock Installed',0dh,0ah
                db      'Type clock s mm:ss to set timer.',0dh,0ah
                db      'Type clock u to remove','$'
alarm_install   DB      'Alarm Installed',0dh,0ah,'$'

over_limit      db      'Over limit !!!',10,13,'$'
Err_Format      db      'Times format error !!!',10,13,'$'

minute          dw      0
second          dw      0
min_limit       dw      60  ;分钟限制
sec_limit       dw      60  ;秒钟限制
alarm_set       db      0   ;闹钟设定与否

ratioA          dw      10  
ratioB          dw      182 
limit_time      equ     10 ;限制闹钟时间,最少10秒

INIT:           MOV     SI,82H ;psp参数起点
                xor     cx,cx 
                mov     cl,[si-2] ;get length ;取参数长度
                jcxz    s70 ;0 表示没有参数
                MOV     AL,[SI]  ;取参数第一byte
                mov     bx,cx 
                mov     byte ptr ds:[si+bx-1],0 ;clear 0dh ;将参数最后回车置0
                ;CMP     BYTE PTR [SI-2],0
                ;JZ      S70
                and     al,11011111b ;转大写
                CMP     AL,'U' ;是否U 
                JZ      S10 ;是,表示要移除
                CMP     AL,'S' ;是否S
                JNZ     S70 ;不是
                CALL    READ_TIME ;读取s参数之后的闹钟设定 如 s 1:10 ;存入delay_count
                mov     alarm_set,0 ;清除
                jc      SHORT S70 ;若cf=1表示闹钟参数错误
                mov     alarm_set,1 cf=0,表示闹钟参数成功,设定alarm_set
                jmp     short s70

S10:            MOV     AX,REMOVE_AX ;询问是否驻留?
                INT     1cH  ;呼叫 
                CMP     BX,EXIST_FLAG ;若已驻留,则会传回驻留标志,是否?
                JZ      S20 ;是 
                MOV     DX,OFFSET NOT_EXIST ;未驻留而有u参数,印出未驻留字串,离开

S15:            MOV     Ah,9
                INT     21H
                MOV     AH,4CH
                INT     21H

S20:            PUSH    AX  ;以下准备移除驻留的工作
                PUSH    AX
                PUSH    DS
                MOV     DS,DX ; 参看104/105行,这时候dx:cx是旧1c中断的原地址
                MOV     DX,CX 
                MOV     AX,251cH ;重置旧1c 中断地址
                INT     21H
                POP     DS
                POP     ES  ; GET TSR CS 取驻留中断的CS放入ES

                MOV     AX,WORD PTR ES:[002CH] ;取cs [2c]中psp地址
                MOV     ES,AX
                MOV     AH,49H ;释放psp内存
                INT     21H
                POP     ES  ;
                MOV     AH,49H ;释放中断内存
                INT     21H
                MOV     DX,OFFSET RELEASE_STR ;离开
                JMP     SHORT S15


S70:            ; Install, ask first ;以下开始做驻留/或设定闹钟工作
                ;

                MOV     AX,REMOVE_AX ;先做询问
                INT     1cH
                CMP     BX,EXIST_FLAG ;是否已驻留?
                jnz     s75 ;不是 ;去驻留
                cmp     alarm_set,1 ;是否设定闹钟
                jz      s77 ;是,去设定闹钟
                MOV     DX,OFFSET EXIST_STR  ;已驻留而又再run程式,又不是设闹钟,所以走人
                Jmp     S15
                ;       Not install

s75:
                MOV     AL,1cH  ;1c中断
                MOV     AH,35H
                INT     21H  ;读取旧1c中断
                MOV     OLD_OFF,BX ;保存
                MOV     OLD_SEG,ES
                MOV     AL,1cH 
                MOV     AH,25H
                MOV     DX,OFFSET INT_START ;我的1ch中断地址
                INT     21H ;设定新1ch中断

s77:            cmp     alarm_set,0  ;是否设定闹钟
                jz      s80  ;不是
                mov     ax,Alarm_set_ask ;设定新闹钟
                mov     cx,delay_count ;放入闹钟计数，子程序read_time已读取
                INT     1cH ;设定
                ;
                mov     dx,offset alarm_install　；闹钟已设字串
                jmp     short s90

s80:            MOV     DX,OFFSET install_str　；已驻留字串
s90:            mov     ah,9
                INT     21H

                MOV     DX,OFFSET tsr_end　；驻留tsr_end之前的代码
                INT     27H

;--------------------------------------------------------------
read_time:　　;以下读取ｓ之后的文字如 11:30等等，先转为总秒数，再乘18.2(1ch中断每秒运行次数)，请自行参详．
                add     si,2 ;point to time set
                mov     di,offset minute
                mov     bx,offset min_limit
                mov     cx,2
                push    cs
                pop     es

rt10:           xor     ax,ax
                lodsb
                cmp     byte ptr [si],':'
                jz      rt15
                cmp     byte ptr [si],0
                jz      rt15
                sub     al,'0'
                mov     ah,al
                lodsb
rt15:           sub     al,'0'
                aad
                cmp     ax,[bx]
                jb      rt20
                mov     dx,offset over_limit
rt16:           mov     ah,9
                int     21h
                jmp     short readtx

rt20:           stosw
                inc     bx
                inc     bx
                dec     cx
                jz      rt50
                lodsb
                cmp     al,':'
                jz      rt10
                cmp     al,0
                jz      rt50

rt40:           mov     dx,offset Err_format
                jmp     short rt16

rt50:           call    count_limit
                jc      rt40
                ret

readtx:         stc
                ret
;------------------------------------------------------------------------------
count_limit:    cmp     minute,0
                jnz     cc20
                cmp     second,0
                jnz     cc20
cc10:           stc
                ret

cc20:           mov     ax,minute
                mov     bx,60
                mul     bx
                add     ax,second
                cmp     ax,limit_time
                jb      cc10
                mul     ratioB
                div     ratioA
                mov     delay_count,ax
                clc
                ret
;------------------------------------------------------------------------------
cseg            ends
                END   begin                   ; end program

